package cn.com.bluemoon.daps.common.datascope.interceptor;

import cn.com.bluemoon.daps.api.sys.RemoteSysOperateLogService;
import cn.com.bluemoon.daps.common.datascope.BmPermission;
import cn.com.bluemoon.daps.common.datascope.log.ILogService;
import cn.com.bluemoon.daps.common.datascope.log.OperateLog;
import cn.com.bluemoon.daps.common.datascope.log.constants.OperateLogConstant;
import cn.com.bluemoon.daps.common.datascope.log.enums.ModuleObject;
import cn.com.bluemoon.daps.common.datascope.log.enums.OperateType;
import cn.com.bluemoon.daps.common.datascope.log.holder.ProcessLogHolder;
import cn.com.bluemoon.daps.common.domain.UserInfoHolder;
import cn.com.bluemoon.daps.common.exception.DapThrowException;
import cn.com.bluemoon.daps.domp.api.IDompService;
import cn.com.bluemoon.daps.domp.api.LocalDompService;
import cn.com.bluemoon.daps.system.entity.DapOperateLog;
import cn.hutool.core.stream.CollectorUtil;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
public class WebOperateLogCollectInterceptor extends HandlerInterceptorAdapter {

    /**
     * 缓存自定义日志收集bean
     */
    private final ConcurrentHashMap<Class<? extends ILogService>, ILogService> beforeMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Class<? extends ILogService>, ILogService> processMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Class<? extends ILogService>, ILogService> afterMap = new ConcurrentHashMap<>();

    private final LocalDompService iDompService;

    private final RemoteSysOperateLogService remoteSysOperateLogService;

    public WebOperateLogCollectInterceptor(LocalDompService iDompService, RemoteSysOperateLogService remoteSysOperateLogService) {
        this.iDompService = iDompService;
        this.remoteSysOperateLogService = remoteSysOperateLogService;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        OperateLog opLog = checkOpLog(handler);
        if (opLog == null) return true;
        String logContent = beforeMap.computeIfAbsent(opLog.before(), getClassILogServiceFunction())
                .collector(opLog, request, response, UserInfoHolder.getUserInfoOrEmpty());
        BmPermission bmPermission = AnnotationUtils.findAnnotation(((HandlerMethod) handler).getMethod(), BmPermission.class);
        InternalLogHolder.set(new InternalLogHolder.Log(opLog, bmPermission,logContent));
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        OperateLog opLog = checkOpLog(handler);
        if (opLog == null) return;
        String processLog = processMap.computeIfAbsent(opLog.process(), getClassILogServiceFunction())
                .collector(opLog, request, response, UserInfoHolder.getUserInfoOrEmpty());
        String afterLog = afterMap.computeIfAbsent(opLog.after(), getClassILogServiceFunction())
                .collector(opLog, request, response, UserInfoHolder.getUserInfoOrEmpty());
        BmPermission bmPermission = AnnotationUtils.findAnnotation(((HandlerMethod) handler).getMethod(), BmPermission.class);
        saveOpLog(opLog, formatLog(InternalLogHolder.get().getLogContent(), processLog, afterLog, opLog.detail()), bmPermission, false);
    }

    private String formatLog(String beforeLog, String processLog, String afterLog, String detail) {
        StringBuilder sb = new StringBuilder();
        if(StringUtils.isNotBlank(detail)){
            sb.append(String.format("自定义日志:\n%s%s;", "  ", detail));
        }
        if (StringUtils.isNotBlank(beforeLog)) {
            sb.append(String.format("\n请求日志:\n%s%s;", "  ", beforeLog));
        }
        if (StringUtils.isNotBlank(processLog)) {
            sb.append(String.format("\n过程日志:\n%s%s;", "  ", processLog));
        }
        if (StringUtils.isNotBlank(afterLog)) {
            sb.append(String.format("\n结束日志:\n%s%s;", "  ", afterLog));
        }
        return sb.toString();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        try{
            if(InternalLogHolder.get() != null){
                OperateLog opLog = InternalLogHolder.get().getOperateLog();
                BmPermission bmPermission = InternalLogHolder.get().getBmPermission();
                // 表示无权限或抛出异常
                if(ProcessLogHolder.get().isFail()){
                    saveOpLog(opLog, ProcessLogHolder.get().getContent(), bmPermission, ProcessLogHolder.get().isFail());
                }
            }
        }finally {
            ProcessLogHolder.clear();
            InternalLogHolder.clear();
        }
    }

    private void saveOpLog(OperateLog operateLog, String logContent, BmPermission bmPermission, boolean isFail) {
        String menuName = operateLog.menuName();
        if (bmPermission != null && bmPermission.value().length > 0) {
            // 获取菜单
            String token =  isFail ? ProcessLogHolder.get().getUserInfo().getToken() : UserInfoHolder.getUserInfo().get().getToken();
            String account = isFail ? ProcessLogHolder.get().getUserInfo().getAccount() : UserInfoHolder.getAccount();
            if(token != null){
                Map<String, List<LocalDompService.MenuDto>> menuMap = iDompService.getMenuPathByTokenAndUserId(token, account);
                if (menuMap != null) {
                    List<List<LocalDompService.MenuDto>> collect = Arrays.stream(bmPermission.value()).filter(m -> menuMap.get(m) != null).map(m -> menuMap.get(m)).collect(Collectors.toList());
                    if (!collect.isEmpty()) {
                        List<LocalDompService.MenuDto> menuDtos = collect.stream().collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll);
                        Set<String> menuList = Arrays.stream(bmPermission.value()).collect(Collectors.toSet());
                        menuName = menuDtos.stream().filter(m -> menuList.contains(m.getMenu_path())).map(LocalDompService.MenuDto::getMenu_name).collect(Collectors.joining(","));
                    }
                }
            }
        }
        saveOpLog(operateLog, logContent, menuName, isFail);
    }

    private void saveOpLog(OperateLog operateLog, String logContent, String menuName, boolean isFail) {
        // 查询菜单
        try {
            DapOperateLog opLog = new DapOperateLog();
            OperateType type = operateLog.type();
            opLog.setOpType(ObjectUtils.isEmpty(type) ? OperateType.UNKNOWN.getType() : type.getType());
            ModuleObject moduleObject = operateLog.moduleName();
            opLog.setModuleName(ObjectUtils.isEmpty(moduleObject) ? ModuleObject.UNKNOWN.getModule() : moduleObject.getModule());
            opLog.setOpUserId(isFail ? ProcessLogHolder.get().getUserInfo().getAccount() : UserInfoHolder.getAccount());
            opLog.setOpTime(LocalDateTime.now());
            opLog.setErrorMessage(null);
            opLog.setOpDetail(logContent);
            opLog.setOpStatus(isFail ? OperateLogConstant.RESULT_FAIL : OperateLogConstant.RESULT_SUCCESS);
            opLog.setCreateTime(LocalDateTime.now());
            opLog.setMenuName(menuName);
            remoteSysOperateLogService.addOperateLog(opLog);
        } catch (Exception e) {
            log.info(e.getMessage(), e);
        }
    }

    static Function<Class<? extends ILogService>, ILogService> getClassILogServiceFunction() {
        return clz -> {
            try {
                return clz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("实例化日志类异常", e);
                throw new DapThrowException("实例化日志类异常", e);
            }
        };
    }

    private OperateLog checkOpLog(Object handler) {
        if (handler instanceof HandlerMethod) {
            OperateLog operateLog = AnnotationUtils.findAnnotation(((HandlerMethod) handler).getMethod(), OperateLog.class);
            return operateLog;
        }
        return null;
    }

    private static final class InternalLogHolder {
        private final static ThreadLocal<Log> holder = ThreadLocal.withInitial(() -> null);

        private InternalLogHolder() {
        }

        @RequiredArgsConstructor
        @Getter
        public static class Log{
            private final OperateLog operateLog;
            private final BmPermission bmPermission;
            private final String logContent;
        }

        public static void set(Log operateLog) {
            holder.set(operateLog);
        }

        public static Log get() {
            return holder.get();
        }

        public static void clear() {
            holder.remove();
        }
    }
}
